package pluginUtils;


public class MobiusTransform {
	protected Complexe a,b,c,d; 
	protected Complexe FixPoint0,FixPoint1; 
	
	public MobiusTransform(Complexe A, Complexe B, Complexe C, Complexe D){
		this.a=new Complexe(A); 
		this.b=new Complexe(B); 
		this.c=new Complexe(C); 
		this.d=new Complexe(D);
		
		this.computeFixPoints(); 
	}
	
	public MobiusTransform(){}
	
	
	public Circle imageCircle(Circle c){
		MobiusTransform y=new MobiusReflection(c); 
		MobiusTransform p1=y.mul(this.inverse()); 
		MobiusTransform p2=this.mul(p1); 
		return p2.matrixToCircle(); 
	}
	
	
	protected Circle matrixToCircle(){
		Complexe center=Complexe.div(this.a,this.c); 
		double radius=Math.abs(1/this.c.getReal()); 
		return new Circle(center,radius); 
	}
	
	
	protected void computeFixPoints(){
		Complexe p1=Complexe.mul(this.a,this.d); 
		Complexe p2=Complexe.mul(this.b,this.c); 
		Complexe f1=Complexe.sub(p1,p2); 
		Complexe f2=Complexe.mulbyDouble(f1,4); 
		Complexe rac=Complexe.mul(this.trace(),this.trace()); 
		rac=Complexe.sub(rac,f2); 
		rac=Complexe.sqrt(rac); 
		Complexe numer=Complexe.sub(this.a,this.d); 
		numer=Complexe.sub(numer,rac);
		FixPoint0=Complexe.div(numer,Complexe.mulbyDouble(this.c,2)); 
		numer=Complexe.sub(this.a,this.d); 
		numer=Complexe.sum(numer,rac);
		FixPoint1=Complexe.div(numer,Complexe.mulbyDouble(this.c,2)); 
	}
	
	public Complexe image(Complexe z){
		Complexe resu=Complexe.mul(this.a,z); 
		resu=Complexe.sum(resu,this.b);
		Complexe denom=Complexe.mul(this.c,z);
		denom=Complexe.sum(denom,this.d); 
		resu=Complexe.div(resu,denom); 
		return resu; 
	}
	
	public Complexe image(Point2D zp){
		Complexe z=Complexe.createComplexeFromRI(zp.getX(), zp.getY());
		Complexe resu=Complexe.mul(this.a,z); 
		resu=Complexe.sum(resu,this.b);
		Complexe denom=Complexe.mul(this.c,z);
		denom=Complexe.sum(denom,this.d); 
		resu=Complexe.div(resu,denom); 
		return resu; 
	}
	
	public MobiusTransform inverse(){
		Complexe det=this.determinant();
		return new MobiusTransform(Complexe.div(this.d,det), Complexe.div(Complexe.opp(this.b),det),
								   Complexe.div(Complexe.opp(this.c),det),Complexe.div(this.a,det)); 
	}
	
	public String toString(){
		String s=""; 
		s=this.a+"\n"+this.b+"\n"+this.c+"\n"+this.d+"\n";
		s+="Determinant :"+this.determinant()+"\n"; 
		s+="Trace : "+this.trace()+"\n"; 
		if(this.isParabolic())s+="Parabolic\n"; 
		if(this.isElliptic())s+="Elliptic\n"; 
		if(this.isLoxodromic())s+="Loxodromic\n"; 
		if(this.isHyperbolic())s+="Hyperbolic\n";
		s+="Fix points : \n";
		s+=FixPoint0+"\n"; 
		s+=FixPoint1+"\n";
		return s; 
	}
	
	public Complexe determinant(){
		Complexe un=Complexe.mul(a,d); 
		Complexe deux=Complexe.mul(c,b);
		return Complexe.sub(un,deux); 
	}
	
	public Complexe trace(){	
		return Complexe.sum(a,d); 
	}
	
	public Complexe pointImage(Complexe z){		
		if(z.isInfinite()) {
			if(!this.c.equals(Complexe.ZERO))
				return Complexe.div(this.a,this.c); 
			else return Complexe.INFINITY;
		}
		else{
		Complexe numer=Complexe.sum(Complexe.mul(a,z),b);
		Complexe denom=Complexe.sum(Complexe.mul(c,z), d);
		if(denom.equals(Complexe.ZERO)) return Complexe.INFINITY;
		else{ 
		return Complexe.div(numer,denom);
		}
		}
	}
	
	public void packCircleImage(PackCircle circle){
		Circle prov=new Circle(Complexe.createComplexeFromRI(circle.getX(), circle.getY()),circle.getRadius());
		Circle p2=circleImage(prov); 
		circle.setRadius(p2.getRadius()); 
		circle.setX(p2.getCenter().getReal()); 
		circle.setY(p2.getCenter().getIm()); 
	}
	
	public Circle circleImage(Circle circle){
		Complexe p=circle.getCenter(); 
		double radius=circle.getRadius(); 
		Complexe cr=Complexe.createComplexeFromRI(radius,0); 
		
		Complexe resu,q; 
		if(!c.equals(Complexe.ZERO)){
		
		resu=Complexe.sum(p,Complexe.div(d,c));
		 
		resu=Complexe.conjugate(resu); 
		 
		resu=Complexe.inverse(resu); 
		resu=Complexe.mulbyDouble(resu,radius*radius);
		resu=Complexe.sub(p,resu);
		q=this.pointImage(resu);
		}
		else q=this.pointImage(p);
		Complexe prov=Complexe.sum(p,cr);
		
		prov=this.pointImage(prov); 
		
		prov=Complexe.sub(q,prov);
		double newRadius=prov.getModule(); 
		return new Circle(q,newRadius);
		
	}
	public AGCircle circleImage(AGCircle circle){
	Circle cc=this.circleImage((Circle) circle);
	return new AGCircle(cc.getCenter(),cc.getRadius(),circle.isInverted(),circle.getColor()); 
	}
	
	
	
	public DoyleCircle doyleCircleImage(DoyleCircle dc){
		Circle u=this.circleImage(dc.getCircle()); 
		return new DoyleCircle(u,dc.getRotation(),dc.getApower(),dc.getBpower()); 
	}
	
	public GeneralizedCircle generalizedCircleImage(GeneralizedCircle c){
		Complexe aa1=Complexe.mulbyDouble(this.a, c.gca); 
		Complexe aa2=Complexe.mul(this.c,c.gcc);
		Complexe A1=Complexe.sum(aa1,aa2); 
		
		aa1=Complexe.mul(this.a, c.gcb); 
		aa2=Complexe.mulbyDouble(this.c,c.gcd);
		Complexe B1=Complexe.sum(aa1,aa2); 
		
		aa1=Complexe.mulbyDouble(this.b,c.gca); 
		aa2=Complexe.mul(this.d,c.gcc);
		Complexe C1=Complexe.sum(aa1,aa2); 
		
		aa1=Complexe.mul(this.b, c.gcb); 
		aa2=Complexe.mulbyDouble(this.d,c.gcd);
		Complexe D1=Complexe.sum(aa1,aa2); 
		
		MobiusTransform auxi=this.conjugate(); 
		
		aa1=Complexe.mul(A1,auxi.a); 
		aa2=Complexe.mul(B1,auxi.c);
		Complexe A2=Complexe.sum(aa1,aa2); 
		
		aa1=Complexe.mul(A1,auxi.b); 
		aa2=Complexe.mul(B1,auxi.d);
		Complexe B2=Complexe.sum(aa1,aa2); 
		
		
		aa1=Complexe.mul(C1,auxi.a); 
		aa2=Complexe.mul(D1,auxi.c);
		Complexe C2=Complexe.sum(aa1,aa2); 
		
		aa1=Complexe.mul(C1,auxi.b); 
		aa2=Complexe.mul(D1,auxi.d);
		Complexe D2=Complexe.sum(aa1,aa2); 
		
		System.out.println(A2+"\n"+B2+"\n"+C2+"\n"+D2); 
		return new GeneralizedCircle(A2.getReal(),B2,C2,D2.getReal()); 
		
	}
	
	public boolean isParabolic(){
		// TODO allow little variations..
		Complexe tr=this.trace(); 
		if(tr.getIm()!=0) return false; 
		return (Math.abs(tr.getReal())==2); 
	}
	
	public boolean isLoxodromic(){
		Complexe tr=this.trace();
		if(tr.getIm()==0) return (Math.abs(tr.getReal())>2); 
		return true; 
	}
	
	public boolean isHyperbolic(){
		Complexe tr=this.trace(); 
		if(tr.getIm()!=0) return false; 
		return (Math.abs(tr.getReal())>2); 
	}
	
	public boolean isElliptic(){
		Complexe tr=this.trace(); 
		if(tr.getIm()!=0) return false;
		return (Math.abs(tr.getReal())<2); 
	}
	
	/** performs this*mt */
	public MobiusTransform mul(MobiusTransform mt){
		Complexe A=Complexe.sum(Complexe.mul(this.a,mt.a),Complexe.mul(this.b,mt.c)); 
		Complexe B=Complexe.sum(Complexe.mul(this.a,mt.b),Complexe.mul(this.b,mt.d));
		Complexe C=Complexe.sum(Complexe.mul(this.c,mt.a),Complexe.mul(this.d,mt.c)); 
		Complexe D=Complexe.sum(Complexe.mul(this.c,mt.b),Complexe.mul(this.d,mt.d));
		return new MobiusTransform(A,B,C,D); 
		
	}

	/**
	 * @return the fixPoint0
	 */
	public Complexe getFixPoint0() {
		return FixPoint0;
	}

	/**
	 * @return the fixPoint1
	 */
	public Complexe getFixPoint1() {
		return FixPoint1;
	}
	
	public MobiusTransform conjugate(){
		return new MobiusTransform(this.a.conjugate(),this.b.conjugate(),this.c.conjugate(),this.d.conjugate()); 
	}
	
	public MobiusTransform transpose(){
		return new MobiusTransform(this.a,this.c,this.b,this.d); 
	}
	
	

}
